package com.darwin.darwinGpuEngine.core.materials {

    import com.adobe.utils.AGALMiniAssembler;
    import com.darwin.darwinGpuEngine.core.GNode;
    import com.darwin.darwinGpuEngine.core.geom.Face;
    import com.darwin.darwinGpuEngine.core.geom.UV;
    import com.darwin.darwinGpuEngine.core.geom.Vertex;
    import com.darwin.darwinGpuEngine.utils.TextureHelper;
    
    import flash.display3D.Context3D;
    import flash.display3D.Context3DProgramType;
    import flash.display3D.Context3DVertexBufferFormat;
    import flash.display3D.Program3D;
    import flash.geom.Point;
    import flash.geom.Rectangle;

    public class GNodeBatchMaterial extends GNodeMaterial {

        protected const DEFAULT_VERTEX_SHADER:String =
                "m44 op, va0, vc[va2.x]             \n" + // vertex * clipspace[idx]
                        "mov vt0, va1                       \n" + // save uv in temp register
                        "mul vt0.xy, vt0.xy, vc[va2.w].zw   \n" + // mult with uv-scale
                        "add vt0.xy, vt0.xy, vc[va2.w].xy   \n" + // add uv offset
                        "mov v0, vt0                        \n" + // copy uv
                        "mov v1, vc[va2.y]	                \n" + // copy colorMultiplier[idx]
                        "mov v2, vc[va2.z]	                \n"; // copy colorOffset[idx]

        /*
         protected const DEFAULT_VERTEX_SHADER:String =
         "m44 op, va0, vc[va2.x]     \n" + // vertex * clipspace[idx]
         "add vt0, va1, vc[va2.z]    \n" + // add uvoffset[idx] to uv coords
         "mov v0, vt0                \n" + // copy uv
         "mov v1, vc[va2.y]	        \n"; // copy color[idx]
         */

        protected const DEFAULT_FRAGMENT_SHADER:String =
                "tex ft0, v0, fs0 <2d,clamp,linear,mipnearest>  \n" + // sample texture from interpolated uv coords
                        "mul ft0, ft0, v1                               \n" + // mult with colorMultiplier
                        "add oc, ft0, v2                               \n"; // add with colorOffset

        protected var constantsPerSprite:uint = 7; // matrix, colorMultiplier, colorOffset, uvoffset
        protected var constantsPerMatrix:uint = 4;

        protected static var cloudProgramData:ProgramData;

        protected const BATCH_SIZE:uint = 126 / constantsPerSprite;

        public static const VERTEX_IDX:String = "PB3D_IDX";

        public function GNodeBatchMaterial() {
            super();
        }

        override public function handleDeviceLoss():void {
            super.handleDeviceLoss();
            cloudProgramData = null;
        }

        override protected function generateBufferData(context:Context3D, faceList:Vector.<Face>):void {

            if(vertexBuffer) {
                return;
            }

            var f0:Face = faceList[0];
            var f1:Face = faceList[1];
            var newF0:Face;
            var newF1:Face;

            var newFaceList:Vector.<Face> = new Vector.<Face>(BATCH_SIZE * 2, true);

            for(var i:int = 0; i < BATCH_SIZE; i++) {
                newF0 = f0.clone();
                newF1 = f1.clone();

                newF0.idx = i;
                newF1.idx = i;

                newFaceList[i * 2] = newF0;
                newFaceList[i * 2 + 1] = newF1;
            }

            super.generateBufferData(context, newFaceList);
        }

        override public function render(context:Context3D, faceList:Vector.<Face>, startTri:uint, numTris:uint):void {
            throw new Error("在此素材中请调用renderBatch方法，不要调用render");
        }

        override protected function prepareForRender(context:Context3D):void {

            context.setProgram(programData.program);
            context.setBlendFactors(blendMode.src, blendMode.dst);
            context.setTextureAt(0, texture.getTexture(context, true));
            context.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_2); // vertex
            context.setVertexBufferAt(1, vertexBuffer, 2, Context3DVertexBufferFormat.FLOAT_2); // uv
            context.setVertexBufferAt(2, vertexBuffer, 4, Context3DVertexBufferFormat.FLOAT_4); // idx
        }

        public function renderBatch(context:Context3D, faceList:Vector.<Face>, childList:Vector.<GNode>):void {

            drawCalls = 0;
            numTris = 0;
            generateBufferData(context, faceList);
			prepareForRender(context);

            var batchLen:uint = 0;
            var child:GNode;
            var colorMultiplierAndOffset:Vector.<Number> = new Vector.<Number>(8, true);
            var uvoffset:Vector.<Number> = new Vector.<Number>(4, true);
            var i:int = -1;
            var n:int = childList.length;
            var offsetFactor:Number = 1.0 / 255.0;

            while(++i < n) {

                child = childList[i];

                if(child.visible) {

                    if(child.invalidateColors) child.updateColors();
                    if(child.invalidateMatrix) child.updateMatrix();

                    var uvOffsetAndScale:Rectangle = new Rectangle(0.0, 0.0, 1.0, 1.0);

                    if(spriteSheet) {

                        uvOffsetAndScale = child.spriteSheet.getUVRectForFrame(texture.textureWidth, texture.textureHeight);

                        var offset:Point = child.spriteSheet.getOffsetForFrame();

                        clipSpaceMatrix.identity();
                        clipSpaceMatrix.appendScale(child.spriteSheet.spriteWidth * 0.5, child.spriteSheet.spriteHeight * 0.5, 1.0);
                        clipSpaceMatrix.appendTranslation(offset.x, offset.y, 0.0);
                        clipSpaceMatrix.append(child.localModelMatrix);
                        clipSpaceMatrix.append(modelMatrix);
                        clipSpaceMatrix.append(viewProjectionMatrix);

                    } else {
                        clipSpaceMatrix.identity();
                        clipSpaceMatrix.appendScale(texture.textureWidth * 0.5, texture.textureHeight * 0.5, 1.0);
                        clipSpaceMatrix.append(child.localModelMatrix);
                        clipSpaceMatrix.append(viewProjectionMatrix);
                    }

                    colorMultiplierAndOffset[0] = child.combinedColorTransform.redMultiplier;
                    colorMultiplierAndOffset[1] = child.combinedColorTransform.greenMultiplier;
                    colorMultiplierAndOffset[2] = child.combinedColorTransform.blueMultiplier;
                    colorMultiplierAndOffset[3] = child.combinedColorTransform.alphaMultiplier;
                    colorMultiplierAndOffset[4] = child.combinedColorTransform.redOffset * offsetFactor;
                    colorMultiplierAndOffset[5] = child.combinedColorTransform.greenOffset * offsetFactor;
                    colorMultiplierAndOffset[6] = child.combinedColorTransform.blueOffset * offsetFactor;
                    colorMultiplierAndOffset[7] = child.combinedColorTransform.alphaOffset * offsetFactor;

                    uvoffset[0] = uvOffsetAndScale.x;
                    uvoffset[1] = uvOffsetAndScale.y;
                    uvoffset[2] = uvOffsetAndScale.width;
                    uvoffset[3] = uvOffsetAndScale.height;

                    context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,
                            batchLen * constantsPerSprite, clipSpaceMatrix, true);

                    context.setProgramConstantsFromVector(Context3DProgramType.VERTEX,
                            batchLen * constantsPerSprite + constantsPerMatrix,
                            colorMultiplierAndOffset);

                    context.setProgramConstantsFromVector(Context3DProgramType.VERTEX,
                            batchLen * constantsPerSprite + constantsPerMatrix + 2,
                            uvoffset);

                    ++batchLen;

                    numTris += 2;

                    if(batchLen == BATCH_SIZE || i == n - 1) {
                        context.drawTriangles(indexBuffer, 0, batchLen * 2);
                        batchLen = 0;
                        ++drawCalls;
                    }
                }
            }

            clearAfterRender(context);
        }

        override protected function clearAfterRender(context:Context3D):void {
            context.setTextureAt(0, null);
            context.setVertexBufferAt(0, null);
            context.setVertexBufferAt(1, null);
            context.setVertexBufferAt(2, null);
        }

        override protected function initProgram(context:Context3D):void {

            if(!cloudProgramData) {
                var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
                vertexShaderAssembler.assemble(Context3DProgramType.VERTEX, DEFAULT_VERTEX_SHADER);

                var colorFragmentShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();
                colorFragmentShaderAssembler.assemble(Context3DProgramType.FRAGMENT, DEFAULT_FRAGMENT_SHADER);

                var program:Program3D = context.createProgram();
                program.upload(vertexShaderAssembler.agalcode, colorFragmentShaderAssembler.agalcode);

                cloudProgramData = new ProgramData(program, 8);
            }

            programData = cloudProgramData;
        }

        override protected function addVertex(context:Context3D, buffer:Vector.<Number>, v:Vertex, uv:UV, face:Face):void {

            fillBuffer(buffer, v, uv, face, VERTEX_POSITION, 2);
            fillBuffer(buffer, v, uv, face, VERTEX_UV, 2);
            fillBuffer(buffer, v, uv, face, VERTEX_IDX, 4);
        }

        override protected function fillBuffer(buffer:Vector.<Number>, v:Vertex, uv:UV, face:Face, semanticsID:String,
                                               floatFormat:int):void {

            if(semanticsID == VERTEX_IDX) {
                // first float will be used for matrix index
                buffer.push(face.idx * constantsPerSprite);
                // second, colorMultiplier idx
                buffer.push(face.idx * constantsPerSprite + constantsPerMatrix);
                // second, colorOffset idx
                buffer.push(face.idx * constantsPerSprite + constantsPerMatrix + 1);
                // third uv offset idx
                buffer.push(face.idx * constantsPerSprite + constantsPerMatrix + 2);

            } else {
                super.fillBuffer(buffer, v, uv, face, semanticsID, floatFormat);
            }
        }

        override public function cleanUp():void {
            super.cleanUp();
        }
    }
}
